#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function

__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
---
module: kubevirt_vm

short_description: Manage KubeVirt virtual machine

description:
    - Use Openshift Python SDK to manage the state of KubeVirt virtual machines.

version_added: "2.8"

author: KubeVirt Team (@kubevirt)

options:
    state:
        description:
            - Set the virtual machine to either I(present), I(absent), I(running) or I(stopped).
            - "I(present) - Create or update virtual machine."
            - "I(absent) - Removes virtual machine."
            - "I(running) - Create or update virtual machine and run it."
            - "I(stopped) - Stops the virtual machine."
        default: "present"
        choices:
            - present
            - absent
            - running
            - stopped
        type: str
    name:
        description:
            - Name of the virtual machine.
        required: true
        type: str
    namespace:
        description:
            - Namespace where the virtual machine exists.
        required: true
        type: str
    ephemeral:
        description:
            - If (true) ephemeral vitual machine will be created. When destroyed it won't be accessible again.
            - Works only with C(state) I(present) and I(absent).
        type: bool
        default: false
    datavolumes:
        description:
            - "DataVolumes are a way to automate importing virtual machine disks onto pvcs during the virtual machine's
               launch flow. Without using a DataVolume, users have to prepare a pvc with a disk image before assigning
               it to a VM or VMI manifest. With a DataVolume, both the pvc creation and import is automated on behalf of the user."
        type: list

extends_documentation_fragment:
  - k8s_auth_options
  - kubevirt_vm_options
  - kubevirt_common_options

requirements:
  - python >= 2.7
  - openshift >= 0.8.2
'''

EXAMPLES = '''
- name: Start virtual machine 'myvm'
  kubevirt_vm:
      state: running
      name: myvm
      namespace: vms

- name: Create virtual machine 'myvm' and start it
  kubevirt_vm:
      state: running
      name: myvm
      namespace: vms
      memory: 64Mi
      cpu_cores: 1
      bootloader: efi
      smbios_uuid: 5d307ca9-b3ef-428c-8861-06e72d69f223
      cpu_model: Conroe
      headless: true
      hugepage_size: 2Mi
      tablets:
        - bus: virtio
          name: tablet1
      cpu_limit: 3
      cpu_shares: 2
      disks:
        - name: containerdisk
          volume:
            containerDisk:
              image: kubevirt/cirros-container-disk-demo:latest
              path: /custom-disk/cirros.img
          disk:
            bus: virtio

- name: Create virtual machine 'myvm' with multus network interface
  kubevirt_vm:
      name: myvm
      namespace: vms
      memory: 512M
      interfaces:
        - name: default
          bridge: {}
          network:
            pod: {}
        - name: mynet
          bridge: {}
          network:
            multus:
              networkName: mynetconf

- name: Combine inline definition with Ansible parameters
  kubevirt_vm:
      # Kubernetes specification:
      definition:
        metadata:
          labels:
            app: galaxy
            service: web
            origin: vmware

      # Ansible parameters:
      state: running
      name: myvm
      namespace: vms
      memory: 64M
      disks:
        - name: containerdisk
          volume:
            containerDisk:
              image: kubevirt/cirros-container-disk-demo:latest
              path: /custom-disk/cirros.img
          disk:
            bus: virtio

- name: Start ephemeral virtual machine 'myvm' and wait to be running
  kubevirt_vm:
      ephemeral: true
      state: running
      wait: true
      wait_timeout: 180
      name: myvm
      namespace: vms
      memory: 64M
      labels:
        kubevirt.io/vm: myvm
      disks:
        - name: containerdisk
          volume:
            containerDisk:
              image: kubevirt/cirros-container-disk-demo:latest
              path: /custom-disk/cirros.img
          disk:
            bus: virtio

- name: Start fedora vm with cloud init
  kubevirt_vm:
      state: running
      wait: true
      name: myvm
      namespace: vms
      memory: 1024M
      cloud_init_nocloud:
        userData: |-
          #cloud-config
          password: fedora
          chpasswd: { expire: False }
      disks:
        - name: containerdisk
          volume:
            containerDisk:
              image: kubevirt/fedora-cloud-container-disk-demo:latest
              path: /disk/fedora.qcow2
          disk:
            bus: virtio

- name: Remove virtual machine 'myvm'
  kubevirt_vm:
      state: absent
      name: myvm
      namespace: vms
'''

RETURN = '''
kubevirt_vm:
  description:
      - The virtual machine dictionary specification returned by the API.
      - "This dictionary contains all values returned by the KubeVirt API all options
         are described here U(https://kubevirt.io/api-reference/master/definitions.html#_v1_virtualmachine)"
  returned: success
  type: complex
  contains: {}
'''


import copy
import traceback

from ansible.module_utils.k8s.common import AUTH_ARG_SPEC

try:
    from openshift.dynamic.client import ResourceInstance
except ImportError:
    # Handled in module_utils
    pass

from ansible.module_utils.k8s.common import AUTH_ARG_SPEC
from ansible.module_utils.kubevirt import (
    virtdict,
    KubeVirtRawModule,
    VM_COMMON_ARG_SPEC,
)

VM_ARG_SPEC = {
    'ephemeral': {'type': 'bool', 'default': False},
    'state': {
        'type': 'str',
        'choices': [
            'present', 'absent', 'running', 'stopped'
        ],
        'default': 'present'
    },
    'datavolumes': {'type': 'list'},
}


class KubeVirtVM(KubeVirtRawModule):

    @property
    def argspec(self):
        """ argspec property builder """
        argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
        argument_spec.update(VM_COMMON_ARG_SPEC)
        argument_spec.update(VM_ARG_SPEC)
        return argument_spec

    def _manage_state(self, running, resource, existing, wait, wait_timeout):
        definition = {'metadata': {'name': self.name, 'namespace': self.namespace}, 'spec': {'running': running}}
        self.patch_resource(resource, definition, existing, self.name, self.namespace, merge_type='merge')

        if wait:
            resource = self.find_supported_resource('VirtualMachineInstance')
            w, stream = self._create_stream(resource, self.namespace, wait_timeout)

        if wait and stream is not None:
            self._read_stream(resource, w, stream, self.name, running)

    def _read_stream(self, resource, watcher, stream, name, running):
        """ Wait for ready_replicas to equal the requested number of replicas. """
        for event in stream:
            if event.get('object'):
                obj = ResourceInstance(resource, event['object'])
                if running:
                    if obj.metadata.name == name and hasattr(obj, 'status'):
                        phase = getattr(obj.status, 'phase', None)
                        if phase:
                            if phase == 'Running' and running:
                                watcher.stop()
                                return
                else:
                    # TODO: wait for stopped state:
                    watcher.stop()
                    return

        self.fail_json(msg="Error waiting for virtual machine. Try a higher wait_timeout value. %s" % obj.to_dict())

    def manage_state(self, state):
        wait = self.params.get('wait')
        wait_timeout = self.params.get('wait_timeout')
        resource_version = self.params.get('resource_version')

        resource_vm = self.find_supported_resource('VirtualMachine')
        existing = self.get_resource(resource_vm)
        if resource_version and resource_version != existing.metadata.resourceVersion:
            return False

        existing_running = False
        resource_vmi = self.find_supported_resource('VirtualMachineInstance')
        existing_running_vmi = self.get_resource(resource_vmi)
        if existing_running_vmi and hasattr(existing_running_vmi.status, 'phase'):
            existing_running = existing_running_vmi.status.phase == 'Running'

        if state == 'running':
            if existing_running:
                return False
            else:
                self._manage_state(True, resource_vm, existing, wait, wait_timeout)
                return True
        elif state == 'stopped':
            if not existing_running:
                return False
            else:
                self._manage_state(False, resource_vm, existing, wait, wait_timeout)
                return True

    def execute_module(self):
        # Parse parameters specific for this module:
        definition = virtdict()
        ephemeral = self.params.get('ephemeral')
        state = self.params.get('state')

        if not ephemeral:
            definition['spec']['running'] = state == 'running'

        # Execute the CURD of VM:
        template = definition if ephemeral else definition['spec']['template']
        kind = 'VirtualMachineInstance' if ephemeral else 'VirtualMachine'
        dummy, definition = self.construct_vm_definition(kind, definition, template)
        result = self.execute_crud(kind, definition)
        changed = result['changed']

        # Manage state of the VM:
        if state in ['running', 'stopped']:
            if not self.check_mode:
                ret = self.manage_state(state)
                changed = changed or ret

        # Return from the module:
        self.exit_json(**{
            'changed': changed,
            'kubevirt_vm': result.pop('result'),
            'result': result,
        })


def main():
    module = KubeVirtVM()
    try:
        module.execute_module()
    except Exception as e:
        module.fail_json(msg=str(e), exception=traceback.format_exc())


if __name__ == '__main__':
    main()
